package common.core.util;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

public class UniqueIdGeneratorUtil {
    private static final AtomicLong workerIdSequence = new AtomicLong(0);
    private static final AtomicLong dataCenterIdSequence = new AtomicLong(0);
    private static Snowflake snowflake;
    private static UniqueIdGeneratorUtil instance;

    private static AtomicBoolean enable = new AtomicBoolean(false);

    public boolean isEnable(){
        return enable.get();
    }

    private UniqueIdGeneratorUtil(){}

    public static UniqueIdGeneratorUtil getInstance(){
        if(instance == null){
            synchronized (UniqueIdGeneratorUtil.class){
                if(instance == null){
                    instance = new UniqueIdGeneratorUtil();
                }
            }
        }
        return instance;
    }

    public void setWorkerIdAndDataCenterIdSequence(long workerIdSeq,long dataCenterIdSeq){
        if(workerIdSeq < 0 || dataCenterIdSeq < 0){
            throw new IllegalArgumentException("Parameter workerIdSeq and dataCenterIdSeq value must be greater than 0");
        }
        workerIdSequence.set(workerIdSeq);
        dataCenterIdSequence.set(dataCenterIdSeq);
        enable.set(true);
    }

    public long getUniqueId(){
        if(!enable.get()){
            throw new UnsupportedOperationException("Method is not activated");
        }

        if(snowflake == null){
            synchronized (Snowflake.class){
                if(snowflake == null){
                    snowflake = new Snowflake(workerIdSequence.get(),dataCenterIdSequence.get());
                }
            }
        }
        return snowflake.nextId();
    }

    private class Snowflake {
        private final long workerId;
        private final long datacenterId;
        private long sequence = 0L;
        private final long twepoch = 1288834974657L; // 起始时间戳，可以根据实际情况调整
        private final long workerIdBits = 5L;
        private final long datacenterIdBits = 5L;
        private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
        private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
        private final long sequenceBits = 12L;
        private final long workerIdShift = sequenceBits;
        private final long datacenterIdShift = sequenceBits + workerIdBits;
        private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        private final long sequenceMask = -1L ^ (-1L << sequenceBits);
        private long lastTimestamp = -1L;

        public Snowflake(long workerId, long datacenterId) {
            if (workerId > maxWorkerId || workerId < 0) {
                throw new IllegalArgumentException("Worker ID can't be greater than " + maxWorkerId + " or less than 0");
            }
            if (datacenterId > maxDatacenterId || datacenterId < 0) {
                throw new IllegalArgumentException("Datacenter ID can't be greater than " + maxDatacenterId + " or less than 0");
            }
            this.workerId = workerId;
            this.datacenterId = datacenterId;
        }

        public synchronized long nextId() {
            long timestamp = timeGen();

            if (timestamp < lastTimestamp) {
                throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
            }

            if (lastTimestamp == timestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }

            lastTimestamp = timestamp;

            return ((timestamp - twepoch) << timestampLeftShift) |
                    (datacenterId << datacenterIdShift) |
                    (workerId << workerIdShift) |
                    sequence;
        }

        private long tilNextMillis(long lastTimestamp) {
            long timestamp = timeGen();
            while (timestamp <= lastTimestamp) {
                timestamp = timeGen();
            }
            return timestamp;
        }

        private long timeGen() {
            return System.currentTimeMillis();
        }
    }
}
